<?php

require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'LzTestClass.php';

/**
 *  Simple testing class.
 *
 *  Test file format:
 *  - - - 8< - - - - - - 8< - - -
 *  <?php
 *  echo 'output';
 *  ?>
 *  ---EXPECT---
 *  output
 *  - - - 8< - - - - - - 8< - - -
 *
 *  Output before '---EXPECT---' and after are right trimed using rtrim()
 *  before comparision. If both parts are equal test was passed.
 *
 *  Tests are executed with error reporting level E_STRICT.
 *
 *  @author     GoniX aka Giedrius Dubinskas
 *  @created    ?
 *  @changed    2007-01-03
 *      * test() includes test files instead of eval()
 *      * Uses glob() instead of dir() (caused to many open files error)
 *      + Test file extension as parameter (allows to have not only test
 *          files in tests directories)
 *      + ln() method to output lines
 *      + Test duration in test results
 *      + Some documentation
 */
class LzTests
{
    const EXPECT  = "\n---EXPECT---\n";


    /**
     *  Constructor.
     *
     *  @param  $ignore     [array] Array of fnmatch() patterns. Files with
     *                      matching paths will be ignored by LzTests::run()
     *                      method. LzTests::test() mothod is not affected.
     *  @param  $ext        [string] Test file extension.
     */
    public
    function __construct($ignore = array(), $ext = '.php')
    {
        $this->ignore   = $ignore;
        $this->ext      = $ext;
    }

    /**
     *  Runs all tests in specified directory recursively.
     *
     *  @param  $testsDir   [string] Path to tests directory.
     *  @return             [array] All tests results in format
     *                      array(
     *                          'path/to/test/file' => [array] results from
     *                                  LzTests::test() method
     *                      );
     */
    public
    function run($testsDir = '')
    {
        $result = array();

        $dir    = rtrim($testsDir, DIRECTORY_SEPARATOR)
                . DIRECTORY_SEPARATOR;

        $result += $this->testFiles($dir, $ext);
        $result += $this->testDirs($dir, $ext);

        return $result;
    }


    /**
     *  Tests file.
     *
     *  @param  $file   [string] Path of test file.
     *  @return         [array] Format:
     *                  array(
     *                      'duration'  => [float] test execution time
     *                      'output'    => [string] test output
     *                      'expected'  => [string] expected output
     *                      'passed'    => [bool] TRUE if test passed
     *                  )
     */
    public
    function test($file)
    {
        $errorReporting     = error_reporting(E_ALL | E_STRICT);

        $startTime          = microtime(true);

        $fix                = array("\r\n" => "\n");
        list($test['output'], $test['expected'])  =
                split(self::EXPECT, strtr(lz_test_exec($file), $fix), 2)
                + array('', '');

        $test['duration']   = microtime(true) - $startTime;
        $test['output']     = rtrim($test['output']);
        $test['expected']   = rtrim($test['expected']);
        $test['passed']     = ($test['output'] == $test['expected']);

        error_reporting($errorReporting);
        return $test;
    }


    /**
     *  Outputs passed arguments each in separate line.
     *
     *  Accepts any number of arguments.
     *
     *  @param  $val    [mixed] Value to output.
     */
    static public
    function ln($val)
    {
        foreach (func_get_args() as $line)
            echo $line . "\n";
    }


    /**
     *  Returns TRUE if test passed.
     *
     *  @param  $test   [array] Result of LzTests::test().
     */
    static public
    function passed($test)
    {
        return isset($test['passed']) && $test['passed'];
    }


    private
    function testFiles($dir, $ext)
    {
        $result = array();

        foreach (glob($dir . '*' . $ext) as $path)
        {
            if (is_file($path) && !$this->ignored($file))
            {
                $result[$path] = $this->test($path);
            }
        }

        return $result;
    }


    private
    function testDirs($dir, $ext)
    {
        $result     = array();

        foreach (glob($dir . '*', GLOB_ONLYDIR) as $path)
        {
            $path   .= DIRECTORY_SEPARATOR;
            $result += $this->testFiles($path, $ext);
            $result += $this->testDirs($path, $ext);
        }

        return $result;
    }


    private
    function ignored($file)
    {
        foreach ($this->ignore as $pattern)
            if (fnmatch($pattern, $file))
                return true;
    }


    private     $ignore = array();
    private     $ext;
}


function lz_test_exec($file)
{
    ob_start();
    include($file);
    return ob_get_clean();
}


function LzTests()
{
    return new LzTests();
}

